package org.example;

import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.highgui.HighGui;

//public class HDevelopLikeImageProcessing {
//    // 加载OpenCV原生库（必须在使用前加载）
//    static {
//        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
//    }
//
//    public static void main(String[] args) {
//        // 1. 读取图像（对应HDevelop的read_image）
//        String imagePath = "C:/Users/baila/Pictures/123.jpg"; // 替换为你的图像路径
//        Mat image = Imgcodecs.imread(imagePath);
//        if (image.empty()) {
//            System.err.println("图像读取失败！请检查路径是否正确。");
//            return;
//        }
//        System.out.println("图像读取成功，尺寸：" + image.rows() + "x" + image.cols());
//
//
//        // 2. 彩色图转灰度图（对应HDevelop的rgb1_to_gray）
//        Mat grayImage = new Mat();
//        Imgproc.cvtColor(image, grayImage, Imgproc.COLOR_BGR2GRAY); // OpenCV默认BGR格式
//
//
//        // 3. 高斯滤波降噪（对应HDevelop的gauss_filter）
//        Mat blurredImage = new Mat();
//        Imgproc.GaussianBlur(grayImage, blurredImage, new Size(5, 5), 1.0); // 5x5卷积核，sigma=1.0
//
//
//        // 4. Sobel边缘检测（对应HDevelop的sobel_amp）
//        Mat sobelX = new Mat();
//        Mat sobelY = new Mat();
//        // 计算X/Y方向梯度（3x3卷积核）
//        Imgproc.Sobel(blurredImage, sobelX, CvType.CV_16S, 1, 0, 3); // X方向
//        Imgproc.Sobel(blurredImage, sobelY, CvType.CV_16S, 0, 1, 3); // Y方向
//        // 转换为绝对值（sum_abs模式）
//        Mat absSobelX = new Mat();
//        Mat absSobelY = new Mat();
//        Core.convertScaleAbs(sobelX, absSobelX);
//        Core.convertScaleAbs(sobelY, absSobelY);
//        // 合并X/Y梯度得到边缘幅度图
//        Mat edgeAmplitude = new Mat();
//        Core.addWeighted(absSobelX, 0.5, absSobelY, 0.5, 0, edgeAmplitude);
//
//
//        // 5. 阈值分割（对应HDevelop的threshold）
//        Mat binaryImage = new Mat();
//        Imgproc.threshold(edgeAmplitude, binaryImage, 50, 255, Imgproc.THRESH_BINARY); // 阈值50，保留>50的区域
//
//
//        // 6. 形态学操作：腐蚀去除噪声（对应HDevelop的erosion）
//        Mat erodedImage = new Mat();
//        Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3)); // 3x3矩形核
//        Imgproc.erode(binaryImage, erodedImage, kernel);
//
//
//        // 7. 提取轮廓（对应HDevelop的gen_contours）
//        java.util.List<MatOfPoint> contours = new java.util.ArrayList<>();
//        Mat hierarchy = new Mat();
//        Imgproc.findContours(erodedImage, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
//
//
//        // 8. 在原始图像上绘制轮廓（对应HDevelop的dev_display + 叠加绘制）
//        Mat result = image.clone();
//        Imgproc.drawContours(result, contours, -1, new Scalar(0, 255, 0), 2); // 绿色轮廓，线宽2
//
//
//        // 9. 显示所有中间结果（对应HDevelop的dev_open_window + dev_display）
//        HighGui.namedWindow("原始图像", HighGui.WINDOW_AUTOSIZE);
//        HighGui.imshow("原始图像", image);
//
//        HighGui.namedWindow("灰度图", HighGui.WINDOW_AUTOSIZE);
//        HighGui.imshow("灰度图", grayImage);
//
//        HighGui.namedWindow("边缘幅度图", HighGui.WINDOW_AUTOSIZE);
//        HighGui.imshow("边缘幅度图", edgeAmplitude);
//
//        HighGui.namedWindow("最终结果（轮廓叠加）", HighGui.WINDOW_AUTOSIZE);
//        HighGui.imshow("最终结果（轮廓叠加）", result);
//
//        HighGui.waitKey(0); // 等待按键输入（0表示无限等待）
//        HighGui.destroyAllWindows(); // 关闭所有窗口
//
//
//        // 10. 释放内存（OpenCV Java需手动释放Mat资源，避免内存泄漏）
//        image.release();
//        grayImage.release();
//        blurredImage.release();
//        sobelX.release();
//        sobelY.release();
//        absSobelX.release();
//        absSobelY.release();
//        edgeAmplitude.release();
//        binaryImage.release();
//        erodedImage.release();
//        kernel.release();
//        hierarchy.release();
//        result.release();
//    }
//}



//import java.util.ArrayList;
//import java.util.List;
////特征提取
//public class HDevelopLikeImageProcessing {
//    static {
//        // 加载OpenCV原生库（必须在使用前加载）
//        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
//    }
//
//    public static void main(String[] args) {
//        // 1. 读取图像（对应HDevelop: read_image(Image,'fabrik')）
//        // 注意：需将'fabrik'图像放在项目根目录或填写绝对路径
//        String imagePath = "C:\\Users\\baila\\Pictures\\success1.png"; // 替换为实际图像路径
//        Mat Image = Imgcodecs.imread(imagePath);
//        if (Image.empty()) {
//            System.err.println("图像读取失败！请检查'fabrik.jpg'是否存在。");
//            return;
//        }
//        System.out.println("图像读取成功，尺寸：" + Image.rows() + "x" + Image.cols());
//
//
//        // 2. 关闭当前图形窗口（对应HDevelop: dev_close_window()）
//        HighGui.destroyAllWindows();
//
//
//        // 3. 打开512x512黑色背景窗口（对应HDevelop: dev_open_window(0,0,512,512,'black',WindowID)）
//        int windowWidth = 512;
//        int windowHeight = 512;
//        String windowName = "HDevelop Equivalent Window";
//        HighGui.namedWindow(windowName, HighGui.WINDOW_NORMAL);
//        HighGui.resizeWindow(windowName, windowWidth, windowHeight);
//        // 创建黑色背景（OpenCV窗口默认黑色，此处显式初始化确保一致性）
//        Mat blackBackground = Mat.zeros(windowHeight, windowWidth, CvType.CV_8UC3);
//        HighGui.imshow(windowName, blackBackground);
//
//
//        // 4. 设置区域绘制模式为填充（对应HDevelop: dev_set_draw('fill')）
//        int drawMode = Imgproc.FILLED; // OpenCV填充模式
//
//
//        // 5. 区域生长算法（对应HDevelop: regiongrowing(Image,Regions,1,1,3,10)）
//        // 注意：OpenCV无内置regiongrowing，此处实现简化版4邻域区域生长
//        Mat grayImage = new Mat();
//        Imgproc.cvtColor(Image, grayImage, Imgproc.COLOR_BGR2GRAY); // 转为灰度图（区域生长基于灰度）
//        List<Mat> Regions = regionGrowing(grayImage, 1, 1, 3, 10); // 自定义区域生长函数
//        if (Regions.isEmpty()) {
//            System.err.println("区域生长未检测到有效区域！");
//            return;
//        }
//
//
//        // 6. 筛选面积为4720的区域（对应HDevelop: select_shape(Regions,A,'area','and',4720,4720)）
//        Mat A = new Mat();
//        int a = 90;
//        for (Mat region : Regions) {
////            double area = Imgproc.contourArea(MatOfPoint.fromMat(region)); // 计算区域面积（像素数）
//            // 原错误代码：
//            // double area = Imgproc.contourArea(MatOfPoint.fromMat(region));
//            // 修正后：
//            double area = Imgproc.contourArea(new MatOfPoint(region)); // 用构造方法转换
//            System.out.println("区域面积：" + area);
//            if (Math.abs(area - a) < 1e-3) { // 允许微小浮点数误差
//                A = region.clone();
//                break;
//            }
//        }
//        if (A.empty()) {
//            System.err.println("未找到面积为"+a+"的区域！");
//            return;
//        }
//
//
//        // 7. 缩小图像域（对应HDevelop: reduce_domain(Image,A,ImageReduced)）
//        Mat mask = Mat.zeros(Image.size(), CvType.CV_8UC1); // 创建掩码（与原图同尺寸）
////        Imgproc.drawContours(mask, List.of(MatOfPoint.fromMat(A)), -1, new Scalar(255), drawMode); // 填充掩码
//        // 原错误代码：
//        // Imgproc.drawContours(mask, List.of(MatOfPoint.fromMat(A)), -1, new Scalar(255), drawMode);
//        // 修正后：
//        Imgproc.drawContours(mask, List.of(new MatOfPoint(A)), -1, new Scalar(255), drawMode);
//        Mat ImageReduced = new Mat();
//        Image.copyTo(ImageReduced, mask); // 仅保留掩码区域内的图像
//
//
//        // 8. 设置绘图颜色为红色（对应HDevelop: dev_set_color('red')）
//        Scalar redColor = new Scalar(0, 0, 255); // OpenCV是BGR格式，红色为(0,0,255)
//
//
//        // 9. 筛选灰度均值190-250的区域（对应HDevelop: select_gray(Regions,Image,SeelectedRegions,'mean','and',190,250)）
//        List<Mat> SelectedRegions = new ArrayList<>();
//        for (Mat region : Regions) {
//            // 计算区域灰度均值
//            Mat regionMask = Mat.zeros(grayImage.size(), CvType.CV_8UC1);
////            Imgproc.drawContours(regionMask, List.of(MatOfPoint.fromMat(region)), -1, new Scalar(255), drawMode);
//            // 正确：用构造方法直接转换Mat为MatOfPoint
//            Imgproc.drawContours(mask, List.of(new MatOfPoint(A)), -1, new Scalar(255), drawMode);
////            Mat meanVal = new Mat();
////            Core.meanStdDev(grayImage, (MatOfDouble) meanVal, (MatOfDouble) new Mat(), regionMask); // 基于掩码计算均值
//
//            // 初始化均值和标准差的输出对象（必须是 MatOfDouble 类型）
//            MatOfDouble meanVal = new MatOfDouble();
//            MatOfDouble stdDevVal = new MatOfDouble();  // 替换原来的 new Mat()
//
//// 调用方法时直接传入，无需强制转换
//            Core.meanStdDev(grayImage, meanVal, stdDevVal, regionMask);
//            double meanGray = meanVal.get(0, 0)[0];
//
//            // 筛选均值在190-250之间的区域
//            if (meanGray >= 190 && meanGray <= 250) {
//                SelectedRegions.add(region.clone());
//            }
//            regionMask.release();
//            meanVal.release();
//        }
//
//
//        // 显示结果（模拟HDevelop的dev_display）
//        Mat display = blackBackground.clone();
//        // 绘制筛选出的红色区域
//        for (Mat region : SelectedRegions) {
//            //Imgproc.drawContours(display, List.of(MatOfPoint.fromMat(region)), -1, redColor, drawMode);
//            // 原错误代码：
//            // Imgproc.drawContours(display, List.of(MatOfPoint.fromMat(region)), -1, redColor, drawMode);
//            // 修正后：
//            Imgproc.drawContours(display, List.of(new MatOfPoint(region)), -1, redColor, drawMode);
//        }
//        HighGui.imshow(windowName, display);
//        HighGui.waitKey(0); // 等待按键关闭窗口
//
//
//        // 释放资源
//        Image.release();
//        grayImage.release();
//        blackBackground.release();
//        A.release();
//        mask.release();
//        ImageReduced.release();
//        display.release();
//        for (Mat region : Regions) region.release();
//        for (Mat region : SelectedRegions) region.release();
//        HighGui.destroyAllWindows();
//    }
//
//
//    /**
//     * 自定义区域生长算法（模拟HDevelop的regiongrowing）
//     * @param grayImage 输入灰度图
//     * @param rowNeighbor 行方向邻域（1=4邻域）
//     * @param colNeighbor 列方向邻域（1=4邻域）
//     * @param threshold 灰度差阈值
//     * @param minArea 最小区域像素数
//     * @return 分割后的区域列表（每个区域为轮廓点集）
//     */
//    private static List<Mat> regionGrowing(Mat grayImage, int rowNeighbor, int colNeighbor, int threshold, int minArea) {
//        List<Mat> regions = new ArrayList<>();
//        Mat visited = Mat.zeros(grayImage.size(), CvType.CV_8UC1); // 标记已访问像素
//
//        for (int i = 0; i < grayImage.rows(); i++) {
//            for (int j = 0; j < grayImage.cols(); j++) {
//                if (visited.get(i, j)[0] == 0) { // 未访问过的像素作为种子点
//                    // 4邻域生长（上下左右）
//                    List<Point> seedPoints = new ArrayList<>();
//                    seedPoints.add(new Point(j, i));
//                    visited.put(i, j, 1);
//                    int seedGray = (int) grayImage.get(i, j)[0];
//
//                    // 生长区域
//                    List<Point> regionPoints = new ArrayList<>();
//                    regionPoints.add(new Point(j, i));
//
//                    // 广度优先搜索（BFS）生长
//                    while (!seedPoints.isEmpty()) {
//                        Point p = seedPoints.remove(0);
//                        // 遍历4邻域
//                        int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; // 上下左右
//                        for (int[] dir : dirs) {
//                            int x = (int) p.x + dir[0];
//                            int y = (int) p.y + dir[1];
//                            // 检查边界和是否已访问
//                            if (x >= 0 && x < grayImage.cols() && y >= 0 && y < grayImage.rows() && visited.get(y, x)[0] == 0) {
//                                int currGray = (int) grayImage.get(y, x)[0];
//                                if (Math.abs(currGray - seedGray) <= threshold) { // 灰度差符合条件
//                                    visited.put(y, x, 1);
//                                    Point newPoint = new Point(x, y);
//                                    seedPoints.add(newPoint);
//                                    regionPoints.add(newPoint);
//                                }
//                            }
//                        }
//                    }
//
//                    // 过滤小于最小面积的区域
//                    if (regionPoints.size() >= minArea) {
//                        // 转换为MatOfPoint格式（OpenCV轮廓格式）
//                        MatOfPoint contour = new MatOfPoint();
//                        contour.fromList(regionPoints);
//                        regions.add(contour);
//                    }
//                }
//            }
//        }
//        visited.release();
//        return regions;
//    }
//}

import java.util.ArrayList;
import java.util.List;

//左右两图片对比
//    public class HDevelopLikeImageProcessing {
//        // 加载OpenCV本地库
//        static {
//            System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
//        }
//
//        public static void main(String[] args) {
//            // 图片路径（实际使用时替换为你的图片路径）
//            String standardImagePath = "C:\\Users\\baila\\Pictures\\111.png";  // 标准参照物图片
//            String testImagePath = "C:\\Users\\baila\\Pictures\\111test3.jpeg";          // 拍摄的车盖图片
//
//            // 读取图片
//            Mat standardMat = Imgcodecs.imread(standardImagePath);
//            Mat testMat = Imgcodecs.imread(testImagePath);
//
//            if (standardMat.empty() || testMat.empty()) {
//                System.err.println("无法读取图片，请检查路径是否正确");
//                return;
//            }
//
//            // 确保两张图片尺寸一致
//            if (standardMat.size().width != testMat.size().width ||
//                    standardMat.size().height != testMat.size().height) {
//                System.out.println("图片尺寸不一致，正在调整测试图片尺寸...");
//                Imgproc.resize(testMat, testMat, standardMat.size());
//            }
//
//            // 转换为HSV色彩空间，更适合颜色差异检测
//            Mat standardHsv = new Mat();
//            Mat testHsv = new Mat();
//            Imgproc.cvtColor(standardMat, standardHsv, Imgproc.COLOR_BGR2HSV);
//            Imgproc.cvtColor(testMat, testHsv, Imgproc.COLOR_BGR2HSV);
//
//            // 计算色差
//            Mat diffMat = new Mat();
//            Core.absdiff(standardHsv, testHsv, diffMat);
//
//            // 分离HSV通道
//            List<Mat> diffChannels = new ArrayList<>();
//            Core.split(diffMat, diffChannels);
//
//            // 设置色差阈值（可根据实际需求调整）
//            double hueThreshold = 15;      // 色调阈值
//            double saturationThreshold = 30; // 饱和度阈值
//            double valueThreshold = 40;     // 明度阈值
//
//            // 对每个通道应用阈值处理
//            Mat hueMask = new Mat();
//            Mat saturationMask = new Mat();
//            Mat valueMask = new Mat();
//            Imgproc.threshold(diffChannels.get(0), hueMask, hueThreshold, 255, Imgproc.THRESH_BINARY);
//            Imgproc.threshold(diffChannels.get(1), saturationMask, saturationThreshold, 255, Imgproc.THRESH_BINARY);
//            Imgproc.threshold(diffChannels.get(2), valueMask, valueThreshold, 255, Imgproc.THRESH_BINARY);
//
//            // 合并掩码，只要有一个通道超过阈值就视为差异区域
//            Mat combinedMask = new Mat();
//            Core.bitwise_or(hueMask, saturationMask, combinedMask);
//            Core.bitwise_or(combinedMask, valueMask, combinedMask);
//
//            // 形态学处理：去除噪声并连接相邻区域
//            Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(5, 5));
//            Imgproc.morphologyEx(combinedMask, combinedMask, Imgproc.MORPH_CLOSE, kernel);
//            Imgproc.morphologyEx(combinedMask, combinedMask, Imgproc.MORPH_OPEN, kernel);
//
//            // 查找差异区域轮廓
//            List<MatOfPoint> contours = new ArrayList<>();
//            Mat hierarchy = new Mat();
//            Imgproc.findContours(combinedMask, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
//
//            // 在测试图片上用红圈标记差异区域
//            Mat markedTestMat = testMat.clone();
//            for (MatOfPoint contour : contours) {
//                // 计算最小外接圆
//                MatOfPoint2f contour2f = new MatOfPoint2f(contour.toArray());
//                float[] radius = new float[1];
//                Point center = new Point();
//                Imgproc.minEnclosingCircle(contour2f, center, radius);
//
//                // 只标记面积足够大的差异区域（过滤小噪声）
//                if (radius[0] > 5) {
//                    Imgproc.circle(markedTestMat, center, (int) radius[0], new Scalar(0, 0, 255), 2);
//                }
//                contour2f.release();
//            }
//
//            // 创建左右对比的结果图片
//            int resultWidth = standardMat.cols() + markedTestMat.cols();
//            int resultHeight = Math.max(standardMat.rows(), markedTestMat.rows());
//            Mat resultMat = new Mat(resultHeight, resultWidth, CvType.CV_8UC3, new Scalar(255, 255, 255));
//
//            // 放置标准图片到左侧
//            Mat leftRegion = resultMat.submat(0, standardMat.rows(), 0, standardMat.cols());
//            standardMat.copyTo(leftRegion);
//            leftRegion.release();
//
//            // 放置标记后的测试图片到右侧
//            Mat rightRegion = resultMat.submat(0, markedTestMat.rows(), standardMat.cols(), resultWidth);
//            markedTestMat.copyTo(rightRegion);
//            rightRegion.release();
//
//            // 添加文字说明
//            Imgproc.putText(resultMat, "标准图片", new Point(standardMat.cols()/3, 30),
//                    Imgproc.FONT_HERSHEY_SIMPLEX, 1, new Scalar(0, 255, 0), 2);
//            Imgproc.putText(resultMat, "检测图片", new Point(standardMat.cols() + standardMat.cols()/3, 30),
//                    Imgproc.FONT_HERSHEY_SIMPLEX, 1, new Scalar(0, 255, 0), 2);
//
//            // 显示结果
//            HighGui.imshow("喷漆色差检测结果", resultMat);
//            HighGui.waitKey(0);
//
//            //如果不想修改releaseResources方法，可以在调用前先释放List中的元素，再释放其他Mat对象。
//
//            // 先释放List<Mat>中的元素
//            for (Mat mat : diffChannels) {
//                mat.release();
//            }
//// 再释放List<MatOfPoint>中的元素（MatOfPoint是Mat的子类）
//            for (MatOfPoint mop : contours) {
//                mop.release();
//            }
//
//            // 释放资源
//            releaseResources(standardMat, testMat, standardHsv, testHsv, diffMat,
//                    diffChannels, hueMask, saturationMask, valueMask,
//                    combinedMask, kernel, hierarchy, contours, markedTestMat, resultMat);
//
//            HighGui.destroyAllWindows();
//        }
//
////        // 释放所有Mat资源
////        private static void releaseResources(Mat... mats) {
////            for (Mat mat : mats) {
////                if (mat != null && !mat.empty()) {
////                    mat.release();
////                }
////            }
////        }
//
//        // 释放资源的方法：支持单个Mat、Mat的List、MatOfPoint的List等
//        public static void releaseResources(Object... resources) {
//            for (Object res : resources) {
//                if (res instanceof Mat) {
//                    // 释放单个Mat
//                    ((Mat) res).release();
//                } else if (res instanceof List<?>) {
//                    // 处理List：遍历释放其中的Mat或MatOfPoint
//                    for (Object item : (List<?>) res) {
//                        if (item instanceof Mat) {
//                            ((Mat) item).release();
//                        } else if (item instanceof MatOfPoint) {
//                            ((MatOfPoint) item).release();
//                        }
//                    }
//                }
//            }
//        }
//
//        // 释放轮廓资源
//        private static void releaseResources(List<MatOfPoint> contours) {
//            for (MatOfPoint contour : contours) {
//                if (contour != null && !contour.empty()) {
//                    contour.release();
//                }
//            }
//        }
//    }


//import org.opencv.core.*;
//import org.opencv.features2d.*;
//import org.opencv.imgcodecs.Imgcodecs;
//import org.opencv.imgproc.Imgproc;
//import org.opencv.calib3d.Calib3d;
//import org.opencv.highgui.HighGui;
////import org.opencv.xfeatures2d.SIFT;
//
//import java.util.ArrayList;
//import java.util.List;
//
////旋转放大缩小识别不生效？
//public class HDevelopLikeImageProcessing {
//    // 加载OpenCV本地库（确保包含xfeatures2d模块，需使用带contrib的OpenCV版本）
//    static {
//        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
//    }
//
//    // 最小匹配点数量阈值（可根据实际场景调整）
//    private static final int MIN_MATCH_COUNT = 10;
//
//    public static void main(String[] args) {
//        // 图片路径（替换为实际路径）
//        String standardImagePath = "C:\\Users\\baila\\Pictures\\111.png";  // 标准图片
//        String testImagePath = "C:\\Users\\baila\\Pictures\\111test2.jpg";  // 测试图片（可能旋转/缩放）
//
//        // 读取图片
//        Mat standardMat = Imgcodecs.imread(standardImagePath);
//        Mat testMat = Imgcodecs.imread(testImagePath);
//
//        if (standardMat.empty() || testMat.empty()) {
//            System.err.println("无法读取图片，请检查路径是否正确");
//            return;
//        }
//
//        // -------------------------- 步骤1：特征提取与匹配（实现旋转/缩放不变性） --------------------------
//        // 转换为灰度图（特征提取通常基于灰度图）
//        Mat standardGray = new Mat();
//        Mat testGray = new Mat();
//        Imgproc.cvtColor(standardMat, standardGray, Imgproc.COLOR_BGR2GRAY);
//        Imgproc.cvtColor(testMat, testGray, Imgproc.COLOR_BGR2GRAY);
//
//        // 初始化SIFT特征检测器（具有尺度和旋转不变性）
//        SIFT sift = SIFT.create();
//
//        // 检测特征点并计算描述符
//        MatOfKeyPoint standardKp = new MatOfKeyPoint();  // 标准图特征点
//        MatOfKeyPoint testKp = new MatOfKeyPoint();      // 测试图特征点
//        Mat standardDescriptors = new Mat();             // 标准图特征描述符
//        Mat testDescriptors = new Mat();                 // 测试图特征描述符
//
//        sift.detectAndCompute(standardGray, new Mat(), standardKp, standardDescriptors);
//        sift.detectAndCompute(testGray, new Mat(), testKp, testDescriptors);
//
//        // 匹配特征描述符（使用FLANN匹配器提高效率）
//        DescriptorMatcher matcher = DescriptorMatcher.create(DescriptorMatcher.FLANNBASED);
//        List<MatOfDMatch> knnMatches = new ArrayList<>();
//        matcher.knnMatch(standardDescriptors, testDescriptors, knnMatches, 2);  // 每个特征点找2个最近邻
//
//        // 应用Lowe's比率测试过滤错误匹配（保留高质量匹配）
//        float ratioThresh = 0.75f;  // 比率阈值（越小越严格）
//        List<DMatch> goodMatchesList = new ArrayList<>();
//        for (MatOfDMatch matches : knnMatches) {
//            if (matches.rows() < 2) continue;  // 确保有2个匹配项
//            DMatch m1 = matches.toArray()[0];
//            DMatch m2 = matches.toArray()[1];
//            if (m1.distance < ratioThresh * m2.distance) {
//                goodMatchesList.add(m1);  // 保留距离更近的匹配
//            }
//        }
//
//        // 判断匹配点数量是否足够（不足则认为不是同一图像）
//        if (goodMatchesList.size() < MIN_MATCH_COUNT) {
//            System.out.println("匹配点数量不足（" + goodMatchesList.size() + "），测试图片与标准图片可能不同");
//            releaseResources(standardMat, testMat, standardGray, testGray,
//                    standardKp, testKp, standardDescriptors, testDescriptors);
//            return;
//        }
//
//        // -------------------------- 步骤2：图像对齐（校正旋转/缩放） --------------------------
//        // 提取匹配的特征点坐标
//        List<Point> standardPoints = new ArrayList<>();
//        List<Point> testPoints = new ArrayList<>();
//        List<KeyPoint> standardKpList = standardKp.toList();
//        List<KeyPoint> testKpList = testKp.toList();
//
//        for (DMatch match : goodMatchesList) {
//            standardPoints.add(standardKpList.get(match.queryIdx).pt);  // 标准图上的点
//            testPoints.add(testKpList.get(match.trainIdx).pt);          // 测试图上的对应点
//        }
//
//        // 转换为MatOfPoint2f格式
//        MatOfPoint2f standardPoints2f = new MatOfPoint2f();
//        MatOfPoint2f testPoints2f = new MatOfPoint2f();
//        standardPoints2f.fromList(standardPoints);
//        testPoints2f.fromList(testPoints);
//
//        // 计算单应矩阵（描述测试图到标准图的变换关系，包含旋转、缩放等）
//        Mat homography = Calib3d.findHomography(testPoints2f, standardPoints2f, Calib3d.RANSAC, 5.0);
//
//        // 根据单应矩阵对齐测试图（将测试图校正为与标准图同视角）
//        Mat alignedTestMat = new Mat();
//        Imgproc.warpPerspective(testMat, alignedTestMat, homography, standardMat.size());
//
//        // -------------------------- 步骤3：对齐后进行色差检测（复用原逻辑） --------------------------
//        // 转换为HSV色彩空间
//        Mat standardHsv = new Mat();
//        Mat alignedTestHsv = new Mat();
//        Imgproc.cvtColor(standardMat, standardHsv, Imgproc.COLOR_BGR2HSV);
//        Imgproc.cvtColor(alignedTestMat, alignedTestHsv, Imgproc.COLOR_BGR2HSV);
//
//        // 计算色差
//        Mat diffMat = new Mat();
//        Core.absdiff(standardHsv, alignedTestHsv, diffMat);
//
//        // 分离HSV通道并应用阈值
//        List<Mat> diffChannels = new ArrayList<>();
//        Core.split(diffMat, diffChannels);
//
//        double hueThreshold = 15;      // 色调阈值（可调整）
//        double saturationThreshold = 30; // 饱和度阈值（可调整）
//        double valueThreshold = 40;     // 明度阈值（可调整）
//
//        Mat hueMask = new Mat();
//        Mat saturationMask = new Mat();
//        Mat valueMask = new Mat();
//        Imgproc.threshold(diffChannels.get(0), hueMask, hueThreshold, 255, Imgproc.THRESH_BINARY);
//        Imgproc.threshold(diffChannels.get(1), saturationMask, saturationThreshold, 255, Imgproc.THRESH_BINARY);
//        Imgproc.threshold(diffChannels.get(2), valueMask, valueThreshold, 255, Imgproc.THRESH_BINARY);
//
//        // 合并掩码（差异区域）
//        Mat combinedMask = new Mat();
//        Core.bitwise_or(hueMask, saturationMask, combinedMask);
//        Core.bitwise_or(combinedMask, valueMask, combinedMask);
//
//        // 形态学处理（去噪声）
//        Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(5, 5));
//        Imgproc.morphologyEx(combinedMask, combinedMask, Imgproc.MORPH_CLOSE, kernel);
//        Imgproc.morphologyEx(combinedMask, combinedMask, Imgproc.MORPH_OPEN, kernel);
//
//        // 标记差异区域
//        List<MatOfPoint> contours = new ArrayList<>();
//        Mat hierarchy = new Mat();
//        Imgproc.findContours(combinedMask, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
//
//        Mat markedAlignedTestMat = alignedTestMat.clone();
//        for (MatOfPoint contour : contours) {
//            MatOfPoint2f contour2f = new MatOfPoint2f(contour.toArray());
//            float[] radius = new float[1];
//            Point center = new Point();
//            Imgproc.minEnclosingCircle(contour2f, center, radius);
//
//            if (radius[0] > 5) {  // 过滤小噪声
//                Imgproc.circle(markedAlignedTestMat, center, (int) radius[0], new Scalar(0, 0, 255), 2);
//            }
//            contour2f.release();
//        }
//
//        // -------------------------- 步骤4：显示结果 --------------------------
//        // 创建对比图（标准图 + 对齐后的测试图 + 差异标记）
//        int resultWidth = standardMat.cols() * 2;
//        int resultHeight = Math.max(standardMat.rows(), markedAlignedTestMat.rows());
//        Mat resultMat = new Mat(resultHeight, resultWidth, CvType.CV_8UC3, new Scalar(255, 255, 255));
//
//        // 放置标准图
//        Mat leftRegion = resultMat.submat(0, standardMat.rows(), 0, standardMat.cols());
//        standardMat.copyTo(leftRegion);
//        leftRegion.release();
//
//        // 放置标记后的对齐测试图
//        Mat rightRegion = resultMat.submat(0, markedAlignedTestMat.rows(), standardMat.cols(), resultWidth);
//        markedAlignedTestMat.copyTo(rightRegion);
//        rightRegion.release();
//
//        // 添加文字说明
//        Imgproc.putText(resultMat, "标准图片", new Point(standardMat.cols()/3, 30),
//                Imgproc.FONT_HERSHEY_SIMPLEX, 1, new Scalar(0, 255, 0), 2);
//        Imgproc.putText(resultMat, "对齐后检测图片", new Point(standardMat.cols() + standardMat.cols()/5, 30),
//                Imgproc.FONT_HERSHEY_SIMPLEX, 1, new Scalar(0, 255, 0), 2);
//
//        // 显示结果
//        HighGui.imshow("旋转/缩放不变的色差检测结果", resultMat);
//        HighGui.waitKey(0);
//
//        // 释放所有资源
//        releaseResources(standardMat, testMat, standardGray, testGray,
//                standardKp, testKp, standardDescriptors, testDescriptors,
//                standardPoints2f, testPoints2f, homography, alignedTestMat,
//                standardHsv, alignedTestHsv, diffMat, diffChannels,
//                hueMask, saturationMask, valueMask, combinedMask, kernel,
//                hierarchy, contours, markedAlignedTestMat, resultMat);
//
//        HighGui.destroyAllWindows();
//    }
//
//    // 释放所有资源（支持Mat、List<Mat>、KeyPoint等）
//    public static void releaseResources(Object... resources) {
//        for (Object res : resources) {
//            if (res instanceof Mat) {
//                ((Mat) res).release();
//            } else if (res instanceof List<?>) {
//                for (Object item : (List<?>) res) {
//                    if (item instanceof Mat) ((Mat) item).release();
//                    else if (item instanceof MatOfPoint) ((MatOfPoint) item).release();
//                    else if (item instanceof MatOfKeyPoint) ((MatOfKeyPoint) item).release();
//                    else if (item instanceof MatOfDMatch) ((MatOfDMatch) item).release();
//                }
//            } else if (res instanceof MatOfKeyPoint) {
//                ((MatOfKeyPoint) res).release();
//            } else if (res instanceof MatOfDMatch) {
//                ((MatOfDMatch) res).release();
//            }
//        }
//    }
//}

import org.opencv.features2d.*;
import org.opencv.calib3d.Calib3d;
import org.opencv.core.Core;
// 1. 导入 DescriptorExtractor 接口（这行是缺失的核心）
//import org.opencv.features2d.DescriptorExtractor;
// 2. 导入 ORB 类（用于初始化接口）
import org.opencv.features2d.ORB;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfDMatch;
import org.opencv.core.MatOfKeyPoint;
import org.opencv.core.MatOfPoint;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.features2d.DescriptorMatcher;
//import org.opencv.features2d.DMatch;
import org.opencv.features2d.ORB; // 必须有
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.calib3d.Calib3d;
import org.opencv.highgui.HighGui;
import java.util.ArrayList;
import java.util.List;

// 特征检测器接口
// 描述符提取器接口
// SIFT特征检测器/提取器实现类
//import org.opencv.features2d.ORB;  // 注释中提到的ORB替代方案（需导入）
//
//import org.opencv.core.Core;
//
//import org.opencv.core.Mat;
//
//
//import org.opencv.core.MatOfDMatch;
//
//
//import org.opencv.core.MatOfKeyPoint;
//
//
//import org.opencv.features2d.DescriptorMatcher;
//
//
//import org.opencv.features2d.ORB;
//
//
//import org.opencv.imgcodecs.Imgcodecs;
//
//
//    public class HDevelopLikeImageProcessing {
//
//
//        static { System.loadLibrary(Core.NATIVE_LIBRARY_NAME); }
//
//
//        public static double compareImages(String filePath1, String filePath2) {
//
//
//            Mat img1 = Imgcodecs.imread(filePath1, Imgcodecs.IMREAD_GRAYSCALE);
//
//
//            Mat img2 = Imgcodecs.imread(filePath2, Imgcodecs.IMREAD_GRAYSCALE);
//
//
//            ORB orb = ORB.create();
//
//
//            MatOfKeyPoint keypoints1 = new MatOfKeyPoint();
//
//
//            MatOfKeyPoint keypoints2 = new MatOfKeyPoint();
//
//
//            Mat descriptors1 = new Mat();
//
//
//            Mat descriptors2 = new Mat();
//
//
//            orb.detectAndCompute(img1, new Mat(), keypoints1, descriptors1);
//
//
//            orb.detectAndCompute(img2, new Mat(), keypoints2, descriptors2);
//
//
//            DescriptorMatcher matcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE_HAMMING);
//
//
//            MatOfDMatch matches = new MatOfDMatch();
//
//
//            matcher.match(descriptors1, descriptors2, matches);
//
//
//            double maxDist = 0; double minDist = 100;
//
//
//            for (DMatch match : matches.toArray()) {
//
//
//                double dist = match.distance;
//
//
//                if (dist < minDist) minDist = dist;
//
//
//                if (dist > maxDist) maxDist = dist;
//
//
//            }
//
//
//            double sum = 0;
//
//
//            for (DMatch match : matches.toArray()) {
//
//
//                if (match.distance <= Math.max(2 * minDist, 30.0)) {
//
//                    sum += match.distance;
//
//                }
//
//
//            }
//
//            //匹配数为 0 则返回一个表示 “完全不相似” 的值（如Double.MAX_VALUE），并抛出提示
//            if (matches.empty()) {
//                return Double.MAX_VALUE; // 无匹配，相似度极低
//            }
//
//            return sum / matches.toArray().length;
//
//
//        }
//
//
//        public static void main(String[] args) {
//
//
//            String filePath1 = "C:\\Users\\baila\\Pictures\\111.png";
//
//
//            String filePath2 = "C:\\Users\\baila\\Pictures\\111test3.jpeg";
//
//
//            System.out.println("Similarity: " + compareImages(filePath1, filePath2));
//
//
//        }
//
//
//    }
//


//import org.opencv.core.*;
////import org.opencv.features2d.DMatch;
//import org.opencv.features2d.DescriptorMatcher;
//import org.opencv.features2d.ORB;
//import org.opencv.imgcodecs.Imgcodecs;
//import org.opencv.imgproc.Imgproc;
//import org.opencv.highgui.HighGui;
//
//import java.util.ArrayList;
//import java.util.HashSet;
//import java.util.List;
//import java.util.Set;
//
////对旋转不免疫
//public class HDevelopLikeImageProcessing {
//
//    // 加载OpenCV本地库
//    static {
//        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
//    }
//
//    // 存储匹配结果的实体类（方便传递数据）
//    static class MatchResult {
//        Mat img1;          // 第一张图（灰度图）
//        Mat img2;          // 第二张图（灰度图）
//        MatOfKeyPoint kp1; // 第一张图的关键点
//        MatOfKeyPoint kp2; // 第二张图的关键点
//        MatOfDMatch matches; // 匹配结果
//        double similarity; // 相似度（越小越相似）
//    }
//
//    /**
//     * 提取特征并计算相似度
//     */
//    public static MatchResult extractFeaturesAndMatch(String filePath1, String filePath2) {
//        MatchResult result = new MatchResult();
//
//        // 1. 读取图片（灰度图）
//        result.img1 = Imgcodecs.imread(filePath1, Imgcodecs.IMREAD_GRAYSCALE);
//        result.img2 = Imgcodecs.imread(filePath2, Imgcodecs.IMREAD_GRAYSCALE);
//        if (result.img1.empty() || result.img2.empty()) {
//            throw new RuntimeException("图片读取失败，请检查路径");
//        }
//
//        // 2. 初始化ORB特征检测器
//        ORB orb = ORB.create(5000); // 最多提取5000个关键点（提升匹配效果）
//        result.kp1 = new MatOfKeyPoint();
//        result.kp2 = new MatOfKeyPoint();
//        Mat desc1 = new Mat();
//        Mat desc2 = new Mat();
//
//        // 3. 检测关键点并计算描述符
//        orb.detectAndCompute(result.img1, new Mat(), result.kp1, desc1);
//        orb.detectAndCompute(result.img2, new Mat(), result.kp2, desc2);
//
//        // 4. 特征匹配（汉明距离，适合ORB的二进制描述符）
//        DescriptorMatcher matcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE_HAMMING);
//        result.matches = new MatOfDMatch();
//        matcher.match(desc1, desc2, result.matches);
//
//        // 5. 计算相似度（优化版：仅用优质匹配的平均距离）
//        List<DMatch> matchList = result.matches.toList();
//        if (matchList.isEmpty()) {
//            result.similarity = Double.MAX_VALUE;
//            return result;
//        }
//
//        // 筛选优质匹配（距离 < 2*最小距离 或 30，取较大值）
//        double minDist = 100, maxDist = 0;
//        for (DMatch m : matchList) {
//            double dist = m.distance;
//            if (dist < minDist) minDist = dist;
//            if (dist > maxDist) maxDist = dist;
//        }
//        List<DMatch> goodMatches = new ArrayList<>();
//        for (DMatch m : matchList) {
//            if (m.distance <= Math.max(2 * minDist, 30.0)) {
//                goodMatches.add(m);
//            }
//        }
//
//        // 计算优质匹配的平均距离（作为相似度指标）
//        result.similarity = goodMatches.isEmpty() ? Double.MAX_VALUE :
////                goodMatches.stream().mapToDouble(DMatch::distance).average().getAsDouble();
//                goodMatches.stream().mapToDouble(m -> m.distance).average().getAsDouble();
//
//        return result;
//    }
//
//    /**
//     * 可视化对比：左右拼接图片，用红圈标记不相似区域（未匹配的特征点）
//     */
//    public static void visualizeDifferences(MatchResult result, String outputPath) {
//        // 1. 将灰度图转为BGR（以便绘制彩色红圈）
//        Mat img1BGR = new Mat();
//        Mat img2BGR = new Mat();
//        Imgproc.cvtColor(result.img1, img1BGR, Imgproc.COLOR_GRAY2BGR);
//        Imgproc.cvtColor(result.img2, img2BGR, Imgproc.COLOR_GRAY2BGR);
//
//        // 2. 调整两张图的高度一致（方便拼接）
//        int height1 = img1BGR.rows();
//        int height2 = img2BGR.rows();
//        int targetHeight = Math.max(height1, height2); // 取最大高度作为目标高度
//
//        // 按比例缩放图像，保持宽高比
//        Mat img1Resized = resizeWithRatio(img1BGR, targetHeight);
//        Mat img2Resized = resizeWithRatio(img2BGR, targetHeight);
//
//        // 3. 左右拼接图片
////        Mat combined = new Mat();
////        Core.hconcat(img1Resized, img2Resized, combined); // 水平拼接
//
//        // 左右拼接图片：先将两个Mat放入List，再调用hconcat
//        Mat combined = new Mat();
//        List<Mat> matsToConcat = new ArrayList<>();
//        matsToConcat.add(img1Resized); // 添加左侧图片
//        matsToConcat.add(img2Resized); // 添加右侧图片
//        Core.hconcat(matsToConcat, combined); // 水平拼接列表中的所有Mat
//
//        // 4. 收集已匹配的关键点索引（用于筛选未匹配的点）
//        Set<Integer> matchedKp1Indices = new HashSet<>(); // img1中已匹配的关键点索引
//        Set<Integer> matchedKp2Indices = new HashSet<>(); // img2中已匹配的关键点索引
//        for (DMatch m : result.matches.toList()) {
//            matchedKp1Indices.add(m.queryIdx); // queryIdx是img1的关键点索引
//            matchedKp2Indices.add(m.trainIdx); // trainIdx是img2的关键点索引
//        }
//
//        // 5. 标记img1中未匹配的关键点（红圈大小=特征点大小）
//        List<KeyPoint> kp1List = result.kp1.toList();
//        for (int i = 0; i < kp1List.size(); i++) {
//            if (!matchedKp1Indices.contains(i)) { // 未匹配的点
//                KeyPoint kp = kp1List.get(i);
//                int circleRadius = (int) (kp.size / 2); // 红圈半径=特征点直径/2
//                // 绘制红圈（半径适配特征点大小）
//                Imgproc.circle(combined, kp.pt, circleRadius, new Scalar(0, 0, 255), 2);
//            }
//        }
//
//// 6. 标记img2中未匹配的关键点（红圈大小=特征点大小）
//        List<KeyPoint> kp2List = result.kp2.toList();
//        int img1Width = img1Resized.cols(); // 左边图的宽度（坐标偏移用）
//        for (int i = 0; i < kp2List.size(); i++) {
//            if (!matchedKp2Indices.contains(i)) { // 未匹配的点
//                KeyPoint kp = kp2List.get(i);
//                int circleRadius = (int) (kp.size / 2); // 红圈半径=特征点直径/2
//                Point adjustedPt = new Point(kp.pt.x + img1Width, kp.pt.y); // 修正右边图坐标
//                // 绘制红圈（半径适配特征点大小）
//                Imgproc.circle(combined, adjustedPt, circleRadius, new Scalar(0, 0, 255), 2);
//            }
//        }
//
////        // 5. 标记img1中未匹配的关键点（红圈）
////        List<KeyPoint> kp1List = result.kp1.toList();
////        for (int i = 0; i < kp1List.size(); i++) {
////            if (!matchedKp1Indices.contains(i)) { // 未匹配的点
////                KeyPoint kp = kp1List.get(i);
////                // 绘制红圈（圆心：kp.pt，半径：5，颜色：红(0,0,255)，线宽：2）
////                Imgproc.circle(combined, kp.pt, 5, new Scalar(0, 0, 255), 2);
////            }
////        }
////
////        // 6. 标记img2中未匹配的关键点（红圈）
////        List<KeyPoint> kp2List = result.kp2.toList();
////        int img1Width = img1Resized.cols(); // 左边图的宽度（用于计算右边图的坐标偏移）
////        for (int i = 0; i < kp2List.size(); i++) {
////            if (!matchedKp2Indices.contains(i)) { // 未匹配的点
////                KeyPoint kp = kp2List.get(i);
////                // 右边图的x坐标需要加上左边图的宽度（才能在拼接图中正确定位）
////                Point adjustedPt = new Point(kp.pt.x + img1Width, kp.pt.y);
////                Imgproc.circle(combined, adjustedPt, 5, new Scalar(0, 0, 255), 2);
////            }
////        }
//
//        // 7. 显示拼接图并保存
//        HighGui.imshow("左右对比（红圈为不相似区域）", combined);
//        HighGui.waitKey(0); // 等待用户关闭窗口
//        Imgcodecs.imwrite(outputPath, combined); // 保存结果到本地
//        HighGui.destroyAllWindows();
//    }
//
//    /**
//     * 按高度比例缩放图像（保持宽高比）
//     */
//    private static Mat resizeWithRatio(Mat src, int targetHeight) {
//        double ratio = (double) targetHeight / src.rows(); // 缩放比例
//        int targetWidth = (int) (src.cols() * ratio);
//        Mat resized = new Mat();
//        Imgproc.resize(src, resized, new Size(targetWidth, targetHeight));
//        return resized;
//    }
//
//    public static void main(String[] args) {
//        // 图片路径（替换为你的图片路径）
//        String filePath1 = "C:\\Users\\baila\\Pictures\\111.png";
//        String filePath2 = "C:\\Users\\baila\\Pictures\\111test3.jpeg";
//        String outputPath = "C:\\Users\\baila\\Pictures\\comparison_result.jpg"; // 结果保存路径
//
//        try {
//            // 提取特征并匹配
//            MatchResult result = extractFeaturesAndMatch(filePath1, filePath2);
//            System.out.println("相似度（平均距离，越小越相似）：" + result.similarity);
//
//            // 可视化对比结果
//            visualizeDifferences(result, outputPath);
//            System.out.println("对比结果已保存至：" + outputPath);
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
//    }
//}


//import org.opencv.core.*;
//import org.opencv.imgcodecs.Imgcodecs;
//import org.opencv.imgproc.Imgproc;
//import org.opencv.highgui.HighGui;
//import org.opencv.imgproc.Moments;
//
//import java.util.*;
//
//public class HDevelopLikeImageProcessing {
//    static {
//        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
//    }
//
//    // 存储形状匹配对的实体类
//    static class ShapePair {
//        int stdIdx;    // 标准图色块索引
//        int testIdx;   // 待测图色块索引
//        Rect stdRect;  // 标准图色块区域
//        Rect testRect; // 待测图色块区域
//    }
//
//    public static void main(String[] args) {
//        // ========== 1. 配置图片路径 ==========
//        String stdPath = "C:/Users/baila/Pictures/111.png";   // 标准图路径
//        String testPath = "C:/Users/baila/Pictures/111test3.jpeg";     // 待测图路径
//        String outputPath = "C:/Users/baila/Pictures/result.png"; // 结果保存路径
//
//        // ========== 2. 读取并预处理图片 ==========
//        Mat stdImg = Imgcodecs.imread(stdPath);
//        Mat testImg = Imgcodecs.imread(testPath);
//        if (stdImg.empty() || testImg.empty()) {
//            System.err.println("图片读取失败，请检查路径！");
//            return;
//        }
//        // 备份标准图（用于拼接，避免原图像被修改）
//        Mat stdImgCopy = stdImg.clone();
//
//        // 转灰度 + 二值化（提取色块，可根据实际场景调整阈值）
//        Mat stdGray = new Mat();
//        Mat testGray = new Mat();
//        Imgproc.cvtColor(stdImg, stdGray, Imgproc.COLOR_BGR2GRAY);
//        Imgproc.cvtColor(testImg, testGray, Imgproc.COLOR_BGR2GRAY);
//
//        Mat stdBinary = new Mat();
//        Mat testBinary = new Mat();
//        Imgproc.threshold(stdGray, stdBinary, 100, 255, Imgproc.THRESH_BINARY_INV);
//        Imgproc.threshold(testGray, testBinary, 100, 255, Imgproc.THRESH_BINARY_INV);
//
//        // ========== 3. 提取色块轮廓（过滤小噪点） ==========
//        List<MatOfPoint> stdContours = new ArrayList<>();
//        Imgproc.findContours(stdBinary, stdContours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
//        List<MatOfPoint> testContours = new ArrayList<>();
//        Imgproc.findContours(testBinary, testContours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
//
//        // 过滤面积过小的轮廓（避免噪点干扰，阈值可调整）
//        filterSmallContours(stdContours, 50);
//        filterSmallContours(testContours, 50);
//
//        // ========== 4. 计算形状特征（Hu矩，用于形状匹配） ==========
//        List<Mat> stdHuMoments = computeHuMoments(stdContours);
//        List<Mat> testHuMoments = computeHuMoments(testContours);
//
//        // ========== 5. 形状匹配：找到相似的色块对 ==========
//        List<ShapePair> similarPairs = matchShapes(
//                stdHuMoments, testHuMoments,
//                stdContours, testContours,
//                0.1  // 形状相似阈值，越小要求越严格，可调整
//        );
//
//        // ========== 6. 颜色对比：标记相似形状的颜色差异（仅标记右边待测图） ==========
//        compareColorsAndMark(stdImg, testImg, similarPairs);
//
//        // ========== 7. 左右拼接图片（左边标准图，右边待测图+色差标记） ==========
//        Mat combinedResult = combineImagesHorizontally(stdImgCopy, testImg);
//
//        // ========== 8. 保存并显示结果 ==========
//        Imgcodecs.imwrite(outputPath, combinedResult);
//        HighGui.imshow("左边：标准图 | 右边：待测图（红圈为色差区域）", combinedResult);
//        HighGui.waitKey(0);
//        HighGui.destroyAllWindows();
//    }
//
//    // 过滤面积过小的轮廓（避免噪点）
//    private static void filterSmallContours(List<MatOfPoint> contours, double minArea) {
//        Iterator<MatOfPoint> iter = contours.iterator();
//        while (iter.hasNext()) {
//            MatOfPoint contour = iter.next();
//            double area = Imgproc.contourArea(contour);
//            if (area < minArea) {
//                iter.remove();
//            }
//        }
//    }
//
//    // 计算轮廓的Hu矩（形状特征）
//    private static List<Mat> computeHuMoments(List<MatOfPoint> contours) {
//        List<Mat> huMoments = new ArrayList<>();
//        for (MatOfPoint contour : contours) {
//            Moments moments = Imgproc.moments(contour);
//            Mat hu = new Mat();
//            Imgproc.HuMoments(moments, hu);
//            huMoments.add(hu);
//        }
//        return huMoments;
//    }
//
//    // 形状匹配：找到相似的色块对
//    private static List<ShapePair> matchShapes(
//            List<Mat> stdHu, List<Mat> testHu,
//            List<MatOfPoint> stdContours, List<MatOfPoint> testContours,
//            double threshold
//    ) {
//        List<ShapePair> pairs = new ArrayList<>();
//        for (int i = 0; i < stdHu.size(); i++) {
//            for (int j = 0; j < testHu.size(); j++) {
//                // 计算形状距离（越小越相似）
//                double dist = Imgproc.matchShapes(stdHu.get(i), testHu.get(j), Imgproc.CONTOURS_MATCH_I1, 0);
//                if (dist < threshold) {
//                    ShapePair pair = new ShapePair();
//                    pair.stdIdx = i;
//                    pair.testIdx = j;
//                    pair.stdRect = Imgproc.boundingRect(stdContours.get(i));
//                    pair.testRect = Imgproc.boundingRect(testContours.get(j));
//                    pairs.add(pair);
//                }
//            }
//        }
//        return pairs;
//    }
//
//    // 对比相似形状的颜色，标记色差区域（仅标记右边待测图）
//    private static void compareColorsAndMark(Mat stdImg, Mat testImg, List<ShapePair> pairs) {
//        // 转换为HSV空间（对颜色差异更鲁棒，不受亮度影响）
//        Mat stdHSV = new Mat();
//        Mat testHSV = new Mat();
//        Imgproc.cvtColor(stdImg, stdHSV, Imgproc.COLOR_BGR2HSV);
//        Imgproc.cvtColor(testImg, testHSV, Imgproc.COLOR_BGR2HSV);
//
//        for (ShapePair pair : pairs) {
//            // 提取标准图和待测图的色块区域
//            Rect stdRect = pair.stdRect;
//            Rect testRect = pair.testRect;
//            Mat stdRoi = new Mat(stdHSV, stdRect);
//            Mat testRoi = new Mat(testHSV, testRect);
//
//            // 统一ROI尺寸（方便逐像素对比）
//            Imgproc.resize(testRoi, testRoi, stdRoi.size());
//
//            // 计算HSV通道差异
//            Mat diff = new Mat();
//            Core.absdiff(stdRoi, testRoi, diff);
//
//            // 拆分通道，筛选色差（H>10、S>30、V>30为明显色差，可调整）
//            List<Mat> diffChannels = new ArrayList<>();
//            Core.split(diff, diffChannels);
//            Mat hDiff = diffChannels.get(0);
//            Mat sDiff = diffChannels.get(1);
//            Mat vDiff = diffChannels.get(2);
//
//            Core.inRange(hDiff, new Scalar(10), new Scalar(255), hDiff);
//            Core.inRange(sDiff, new Scalar(30), new Scalar(255), sDiff);
//            Core.inRange(vDiff, new Scalar(30), new Scalar(255), vDiff);
//
//            // 合并差异通道，标记色差区域
//            Mat mask = new Mat();
//            Core.bitwise_or(hDiff, sDiff, mask);
//            Core.bitwise_or(mask, vDiff, mask);
//
//            // 膨胀差异区域（让红圈更明显）
//            Imgproc.dilate(mask, mask, Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(3, 3)));
//
//            // 若存在色差，在待测图上标记红圈（仅右边标记）
//            if (Core.countNonZero(mask) > 0) {
//                Point center = new Point(testRect.x + testRect.width / 2, testRect.y + testRect.height / 2);
//                int radius = (int) (Math.max(testRect.width, testRect.height) / 2);
//                Imgproc.circle(testImg, center, radius, new Scalar(0, 0, 255), 2);
//            }
//        }
//    }
//
//    // 辅助方法：水平拼接两张图片（保持高度一致，比例不变）
//    private static Mat combineImagesHorizontally(Mat img1, Mat img2) {
//        // 调整两张图高度一致
//        int targetHeight = Math.max(img1.rows(), img2.rows());
//        Mat img1Resized = resizeWithRatio(img1, targetHeight);
//        Mat img2Resized = resizeWithRatio(img2, targetHeight);
//
//        // 水平拼接
//        Mat combined = new Mat();
//        List<Mat> mats = new ArrayList<>();
//        mats.add(img1Resized);
//        mats.add(img2Resized);
//        Core.hconcat(mats, combined);
//
//        return combined;
//    }
//
//    // 辅助方法：按高度比例缩放图像（保持宽高比）
//    private static Mat resizeWithRatio(Mat src, int targetHeight) {
//        double ratio = (double) targetHeight / src.rows();
//        int targetWidth = (int) (src.cols() * ratio);
//        Mat resized = new Mat();
//        Imgproc.resize(src, resized, new Size(targetWidth, targetHeight));
//        return resized;
//    }
//}

import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.highgui.HighGui;
import org.opencv.imgproc.Moments;

import java.util.*;

public class HDevelopLikeImageProcessing {
    // 静态加载OpenCV库
    static {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }

    // 色块实体类：存储轮廓、面积、颜色均值、中心坐标等信息
    static class ColorBlock {
        MatOfPoint contour;  // 轮廓
        double area;         // 面积
        double[] hsvMean;    // HSV颜色均值 [H, S, V]
        Point center;        // 中心坐标
        double radius;       // 外接圆半径（用于画红圈）
    }

    public static void main(String[] args) {
        // 1. 配置参数（根据实际场景调整）
        String stdPath = "C:/Users/baila/Pictures/111.png";   // 标准图路径
        String testPath = "C:/Users/baila/Pictures/111test3.jpeg";  // 实时拍摄图路径
        String outputPath = "C:/Users/baila/Pictures/paint_detection_result.png";  // 结果保存路径
        double minArea = 500;  // 最小色块面积（过滤小噪点，汽车车漆建议500-2000）
        double colorDiffThresh = 30;  // 色差阈值（值越大容忍度越高，建议20-40）
        int morphKernelSize = 5;  // 形态学去噪核大小

        // 2. 读取并预处理图片（统一尺寸，减少匹配误差）
        Mat stdImg = Imgcodecs.imread(stdPath);
        Mat testImg = Imgcodecs.imread(testPath);
        if (stdImg.empty() || testImg.empty()) {
            System.err.println("图片读取失败，请检查路径！");
            return;
        }

        // 统一图片尺寸（实时图可能因拍摄距离缩放）
        Mat stdResized = new Mat();
        Mat testResized = new Mat();
        Size targetSize = new Size(stdImg.cols(), stdImg.rows());
        Imgproc.resize(stdImg, stdResized, targetSize);
        Imgproc.resize(testImg, testResized, targetSize);

        // 3. 提取标准图和实时图的色块
        List<ColorBlock> stdBlocks = extractColorBlocks(stdResized, minArea, morphKernelSize);
        List<ColorBlock> testBlocks = extractColorBlocks(testResized, minArea, morphKernelSize);

        if (stdBlocks.isEmpty() || testBlocks.isEmpty()) {
            System.err.println("未检测到有效色块！");
            return;
        }

        // 4. 为色块标注不同颜色（标准图和实时图）
        Mat stdLabeled = labelColorBlocks(stdResized.clone(), stdBlocks);
        Mat testLabeled = labelColorBlocks(testResized.clone(), testBlocks);

        // 5. 按面积最相近原则匹配色块
        List<Map.Entry<ColorBlock, ColorBlock>> matchedPairs = matchBlocksByArea(stdBlocks, testBlocks);

        // 6. 计算色差并标注超标区域
        Mat testWithRedCircle = markColorDiff(testLabeled, matchedPairs, colorDiffThresh);

        // 7. 左右拼接结果图（左：标准图；右：实时图+红圈）
        Mat result = combineImagesHorizontally(stdLabeled, testWithRedCircle);

        // 8. 保存并显示结果
        Imgcodecs.imwrite(outputPath, result);
        HighGui.imshow("汽车车漆检测结果（左：标准图 | 右：实时图，红圈为色差超标）", result);
        HighGui.waitKey(0);
        HighGui.destroyAllWindows();
    }

    /**
     * 提取图片中的色块（轮廓），并计算面积、颜色均值等信息
     */
    private static List<ColorBlock> extractColorBlocks(Mat img, double minArea, int morphKernelSize) {
        List<ColorBlock> blocks = new ArrayList<>();

        // 转换为HSV空间（抗光照干扰，适合颜色检测）
        Mat hsv = new Mat();
        Imgproc.cvtColor(img, hsv, Imgproc.COLOR_BGR2HSV);

//        // 提取亮度通道（V通道）用于轮廓检测（不受颜色影响）
//        List<Mat> hsvChannels = new ArrayList<>();
//        Core.split(hsv, hsvChannels);
//        Mat vChannel = hsvChannels.get(2);

        // 在extractColorBlocks方法中，拆分HSV通道后：
        List<Mat> hsvChannels = new ArrayList<>();
        Core.split(hsv, hsvChannels);  // 拆分得到H、S、V三个单通道Mat
        Mat hChannel = hsvChannels.get(0);  // H通道
        Mat sChannel = hsvChannels.get(1);  // S通道
        Mat vChannel = hsvChannels.get(2);  // V通道

        // 预处理：高斯模糊去噪 + 形态学闭运算填充缝隙
        Mat blurred = new Mat();
        Imgproc.GaussianBlur(vChannel, blurred, new Size(5, 5), 0);
        Mat morph = new Mat();
        Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(morphKernelSize, morphKernelSize));
        Imgproc.morphologyEx(blurred, morph, Imgproc.MORPH_CLOSE, kernel);

        // 二值化提取轮廓
        Mat binary = new Mat();
        Imgproc.threshold(morph, binary, 0, 255, Imgproc.THRESH_BINARY_INV + Imgproc.THRESH_OTSU);

        // 查找轮廓
        List<MatOfPoint> contours = new ArrayList<>();
        Imgproc.findContours(binary, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

        // 过滤小面积轮廓，计算色块信息
        for (MatOfPoint contour : contours) {
            double area = Imgproc.contourArea(contour);
            if (area < minArea) continue;  // 过滤小噪点

            ColorBlock block = new ColorBlock();
            block.contour = contour;
            block.area = area;

            // 计算中心坐标和外接圆半径
            Moments moments = Imgproc.moments(contour);
            block.center = new Point(moments.m10 / moments.m00, moments.m01 / moments.m00);
            MatOfPoint2f contour2f = new MatOfPoint2f(contour.toArray());
            float[] radius = new float[1];
            Point center = new Point();
            Imgproc.minEnclosingCircle(contour2f, center, radius);
            block.radius = radius[0];

//            // 计算HSV颜色均值（仅色块区域）
            Mat mask = Mat.zeros(img.size(), CvType.CV_8UC1);
            Imgproc.drawContours(mask, Collections.singletonList(contour), -1, new Scalar(255), -1);
//            block.hsvMean = calculateHSVMean(hsv, mask);


// 计算HSV颜色均值时，传入三个单通道Mat和掩码
            block.hsvMean = calculateHSVMean(hChannel, sChannel, vChannel, mask);

            blocks.add(block);
        }

        return blocks;
    }

    /**
     * 计算指定区域的HSV颜色均值（修正版）
     * @param hChannel H通道单通道图像
     * @param sChannel S通道单通道图像
     * @param vChannel V通道单通道图像
     * @param mask 掩码（仅计算掩码内的像素均值）
     */
    private static double[] calculateHSVMean(Mat hChannel, Mat sChannel, Mat vChannel, Mat mask) {
        // 注意：mean方法在Core类中，而非Imgproc
        Scalar meanH = Core.mean(hChannel, mask);  // H通道均值
        Scalar meanS = Core.mean(sChannel, mask);  // S通道均值
        Scalar meanV = Core.mean(vChannel, mask);  // V通道均值
        return new double[]{meanH.val[0], meanS.val[0], meanV.val[0]};
    }

    /**
     * 为每个色块标注不同颜色（边缘标注，不遮挡原图）
     */
    private static Mat labelColorBlocks(Mat img, List<ColorBlock> blocks) {
        Mat labeled = img.clone();
        // 预定义20种区分明显的颜色（足够汽车色块检测）
        Scalar[] colors = {
                new Scalar(255, 0, 0),    // 蓝
                new Scalar(0, 255, 0),    // 绿
                new Scalar(0, 0, 255),    // 红
                new Scalar(255, 255, 0),  // 青
                new Scalar(255, 0, 255),  // 洋红
                new Scalar(0, 255, 255),  // 黄
                new Scalar(128, 0, 0),    // 深蓝
                new Scalar(0, 128, 0),    // 深绿
                new Scalar(0, 0, 128),    // 深红
                new Scalar(128, 128, 0),  // 深青
                new Scalar(128, 0, 128),  // 深洋红
                new Scalar(0, 128, 128),  // 深黄
                new Scalar(255, 128, 0),  // 橙
                new Scalar(255, 0, 128),  // 粉
                new Scalar(128, 255, 0),  // 浅绿
                new Scalar(0, 255, 128),  // 浅青
                new Scalar(128, 0, 255),  // 紫
                new Scalar(0, 128, 255),  // 浅蓝
                new Scalar(255, 128, 128),// 浅红
                new Scalar(128, 128, 128) // 灰
        };

        // 为每个色块绘制边缘（不同颜色）
        for (int i = 0; i < blocks.size(); i++) {
            Scalar color = colors[i % colors.length];  // 循环使用颜色
            Imgproc.drawContours(labeled, Collections.singletonList(blocks.get(i).contour), -1, color, 2);
        }

        return labeled;
    }

    /**
     * 按面积最相近原则匹配标准图和实时图的色块
     */
    private static List<Map.Entry<ColorBlock, ColorBlock>> matchBlocksByArea(
            List<ColorBlock> stdBlocks, List<ColorBlock> testBlocks) {
        List<Map.Entry<ColorBlock, ColorBlock>> matchedPairs = new ArrayList<>();
        Set<Integer> matchedTestIndices = new HashSet<>();  // 记录已匹配的实时图色块索引

        // 为标准图每个色块匹配面积最接近的实时图色块
        for (ColorBlock stdBlock : stdBlocks) {
            double minAreaDiff = Double.MAX_VALUE;
            ColorBlock bestMatch = null;
            int bestTestIdx = -1;

            for (int i = 0; i < testBlocks.size(); i++) {
                if (matchedTestIndices.contains(i)) continue;  // 跳过已匹配的

                ColorBlock testBlock = testBlocks.get(i);
                double areaDiff = Math.abs(stdBlock.area - testBlock.area);
                if (areaDiff < minAreaDiff) {
                    minAreaDiff = areaDiff;
                    bestMatch = testBlock;
                    bestTestIdx = i;
                }
            }

            if (bestMatch != null) {
                matchedPairs.add(new AbstractMap.SimpleEntry<>(stdBlock, bestMatch));
                matchedTestIndices.add(bestTestIdx);
            }
        }

        return matchedPairs;
    }

    /**
     * 计算配对色块的色差，超标则在实时图上用红圈标注
     */
    private static Mat markColorDiff(Mat testLabeled,
                                     List<Map.Entry<ColorBlock, ColorBlock>> matchedPairs,
                                     double threshold) {
        Mat marked = testLabeled.clone();
        Imgproc.putText(marked, "红圈：色差超标区域", new Point(10, 30),
                Imgproc.FONT_HERSHEY_SIMPLEX, 0.8, new Scalar(0, 0, 255), 2);

        for (Map.Entry<ColorBlock, ColorBlock> pair : matchedPairs) {
            ColorBlock stdBlock = pair.getKey();
            ColorBlock testBlock = pair.getValue();

            // 计算HSV三通道综合色差（欧氏距离）
            double hDiff = Math.abs(stdBlock.hsvMean[0] - testBlock.hsvMean[0]);
            double sDiff = Math.abs(stdBlock.hsvMean[1] - testBlock.hsvMean[1]);
            double vDiff = Math.abs(stdBlock.hsvMean[2] - testBlock.hsvMean[2]);
            double colorDiff = Math.sqrt(hDiff * hDiff + sDiff * sDiff + vDiff * vDiff);

            // 色差超标，画红圈并标注数值
            if (colorDiff > threshold) {
                // 红圈略大于色块外接圆，突出显示
                Imgproc.circle(marked, testBlock.center, (int) (testBlock.radius + 5),
                        new Scalar(0, 0, 255), 3);
                // 标注色差数值
                Imgproc.putText(marked, String.format("%.1f", colorDiff),
                        new Point(testBlock.center.x - 20, testBlock.center.y - testBlock.radius - 10),
                        Imgproc.FONT_HERSHEY_SIMPLEX, 0.6, new Scalar(0, 0, 255), 2);
            }
        }

        return marked;
    }

    /**
     * 水平拼接两张图片（保持高度一致）
     */
    private static Mat combineImagesHorizontally(Mat img1, Mat img2) {
        // 调整高度一致
        int targetHeight = Math.max(img1.rows(), img2.rows());
        Mat img1Resized = resizeWithRatio(img1, targetHeight);
        Mat img2Resized = resizeWithRatio(img2, targetHeight);

        // 水平拼接
        Mat combined = new Mat();
        List<Mat> mats = new ArrayList<>();
        mats.add(img1Resized);
        mats.add(img2Resized);
        Core.hconcat(mats, combined);

        return combined;
    }

    /**
     * 按高度比例缩放图片（保持宽高比）
     */
    private static Mat resizeWithRatio(Mat src, int targetHeight) {
        double ratio = (double) targetHeight / src.rows();
        int targetWidth = (int) (src.cols() * ratio);
        Mat resized = new Mat();
        Imgproc.resize(src, resized, new Size(targetWidth, targetHeight));
        return resized;
    }
}


//// 替换为你的图片路径（左边参照，右边待检测）
//String refPath = "C:\\Users\\baila\\Pictures\\111.png"; // 参照图（左边）
//String testPath = "C:\\Users\\baila\\Pictures\\111test3.jpeg"; // 待检测图（右边，可能旋转/颜色偏差）
//String outputPath = "C:\\Users\\baila\\Pictures\\diff_result.jpg";